#include "builtins.hpp"
#include "utils.hpp"
#include "parser.hpp"
#include "lexer.hpp"
#include "io.hpp"

#include <fstream>
#include <sstream>
#include <ctime>
#include <cmath>
#include <cstdlib>

Variable bi_set(vector<Variable>& argv, Environment& env)
{
	Variable ret=0;
	int argc = (int)argv.size();
	if(argc != 4) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	if(argv[1].type != tVarname) {
		throw_error(ERROR_ARGUMENTS);
	}
	Variable *lval = &env.getvar(argv[1].str);
	Variable &rval = argv[3];
	env.verify(rval);
	Variable &op = argv[2];
	if(op.type != tOpcode) {
		throw_error(ERROR_ARGUMENTS);
	}
	
	return (*lval = _bi_math_solve(*lval, op, rval, true));
}

Variable bi_typeof(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=2) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable &obj = argv[1];
	env.verify(obj);
	ret.str = vartypeToStr(obj.type);
	ret.i = obj.type;
	ret.type = tTypeID;
	return ret;
}

Variable bi_null(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc == 1) {
		ret = env.getvar("__return__");
	} else {
		ret = argv.back();
	}
	env.verify(ret);
	return ret;
}

Variable bi_return(vector<Variable>& argv, Environment& env)
{
	unsigned argc = argv.size();
	if(argc > 2) {
		throw(ERROR_ARGUMENTCOUNT);
	}
	if(argc == 2) {
		Variable &ret = argv[1];
		env.verify(ret);
		env.getvar("__return__") = ret;
	}
	throw(ERROR_RETURNING);
	return env.getvar("__return__"); // NO-OP, but it's needed to shut the compiler up
}

Variable bi_core(vector<Variable>& argv, Environment &env) {
	unsigned argc = argv.size();
	if(argc < 2) {
		throw(ERROR_ARGUMENTCOUNT);
	}
	if(env._func.find(argv[1].str) != env._func.end()) {
		// The builtin function with that name exists
		argv.erase(argv.begin());
		return (env._func[argv[0].str](argv,env));
	} else {
		throw(ERROR_ALIAS);
	}
	return env.getvar("__return__");
}

Variable bi_exit(vector<Variable>& argv, Environment& env)
{
	UNUSED env;
	UNUSED argv;
	exit(0);
}

Variable bi_substr(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc != 4) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable &source = argv[1];
	env.verify(source);
	Variable &start = argv[2];
	env.verify(start);
	Variable &stop = argv[3];
	env.verify(stop);
	if(source.type!=tString) {
		throw_error(ERROR_ARGUMENTS);
	}
	if( (start.type != tNumeric) || (stop.type!=tNumeric) ) {
		throw_error(ERROR_ARGUMENTS);
	}
	ret.str = source.str.substr((int)start.i, int(stop.i-start.i)+1);
	ret.type = tString;
	return ret;
}

Variable bi_strlen(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc != 2) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable &a = argv[1];
	env.verify(a);
	if(a.type!=tString) {
		throw_error(ERROR_ARGUMENTS);
	}
	ret = a.str.size();
	return ret;
}

////////////////////////////////////////////////////////////////////////////////
Variable bi_if(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc != 3 && argc != 5) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable condition = argv[1];
	env.verify(condition);
	vector<Variable>cmd;
	if(argc==3) {
		if(argv[2].type != tInline) {
			throw_error(ERROR_INLINECODE);
		}
		if( condition.i ) {
			cmd = tokenize(argv[2].str,env,DISABLESCOPE);
			parse(cmd,env);
			ret = env.getvar("__return__");
		}
		return ret;
	} else if(argc==5) {
		if( (argv[2].type != tInline) || (argv[4].type != tInline) ) {
			throw_error(ERROR_INLINECODE);
		}
		Variable b = argv[3];
		env.verify(b);
		if( condition.i ) {
			cmd = tokenize(argv[2].str,env,DISABLESCOPE);
		} else {
			cmd = tokenize(argv[4].str,env,DISABLESCOPE);
		}
		parse(cmd,env);
		ret = env.getvar("__return__");
		return ret;
	} else {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
}

Variable bi_switch(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc < 4) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	
	Variable &value = argv[1];
	env.verify(value);
	Variable tmp;
	
	for(int i=2;i<argc;i++) {
		tmp = argv[i];
		if(tmp.str == "default") {
			for(i++;i<argc;i++) {
				tmp = argv[i];
				env.verify(tmp);
				if(tmp.type == tInline) {
					return orate(tmp.str, env, DISABLESCOPE);
				}
			}
		}
		env.verify(tmp);
		if(tmp == value) {
			for(i++;i<argc;i++) {
				tmp = argv[i];
				env.verify(tmp);
				if(tmp.type == tInline) {
					return orate(tmp.str, env, DISABLESCOPE);
				}
			}
		}
	}
	return ret;
}

Variable bi_for(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if( (argc!=3) && (argc!=5) ) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable cond, // Condition for continuing
	incr, // Incrementation section of loop
	init, // Initialization section of loop
	amount, // Amount of times to run
	code; // The actual code
	env.inscope();
	if(argc==3) {
		// Simplified loop
		code = argv[2];
		if(code.type != tInline) {
			throw_error(ERROR_ARGUMENTS);
		}
		amount = argv[1];
		env.verify(amount);
		if(amount.type != tNumeric) {
			throw_error(ERROR_ARGUMENTS);
		}
		env._var.back()["__iterator__"] = Variable();
		init = (Variable)"set __iterator__ = 0";
		init.type = tInline;
		cond = (Variable)("math __iterator__ < " + amount.str);
		cond.type = tInline;
		incr = (Variable)"set __iterator__ + 1";
		incr.type = tInline;
	} else {
		// It has to be 5, because of the previous checks
		code = argv[4];
		if(code.type != tInline) {
			throw_error(ERROR_ARGUMENTS);
		}
		amount = 0;
		init = argv[1];
		if(init.type != tInline) {
			throw_error(ERROR_ARGUMENTS);
		}
		cond = argv[2];
		if(cond.type != tInline) {
			throw_error(ERROR_ARGUMENTS);
		}
		incr = argv[3];
		if(incr.type != tInline) {
			throw_error(ERROR_ARGUMENTS);
		}
	}
	orate(init.str,env,DISABLESCOPE);
	while(orate(cond.str,env,DISABLESCOPE).i) {
		orate(code.str,env,DISABLESCOPE);
		ret = env.getvar("__return__");
		orate(incr.str,env,DISABLESCOPE);
		env.getvar("__return__") = ret;
		if(env.flags & Break_Scope) {
			env.flags ^= Break_Scope;
			break;
		}
	}
	env.outscope();
	return ret;
}

Variable bi_foreach(vector<Variable>& argv, Environment& env)
{
	// Syntax: foreach arr name {code}
	Variable ret;
	int argc = (int)argv.size();
	if( argc!=4 ) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable &arr = argv[1];
	env.verify(arr);
	if(arr.type != tArray) {
		throw_error(ERROR_ARGUMENTS);
	}
	Variable &name = argv[2];
	if(name.type != tVarname) {
		throw_error(ERROR_ARGUMENTS);
	}
	Variable &code = argv[3];
	if(code.type != tInline) {
		throw_error(ERROR_INLINECODE);
	}
	for(unsigned i=0;i<arr.arr.size();i++) {
		env.getvar(name.str) = arr.arr[i];
		orate(code.str,env,DISABLESCOPE);
		ret = env.getvar("__return__");
		if(env.flags & Break_Scope) {
			env.flags ^= Break_Scope;
			break;
		}
	}
	return ret;
}

Variable bi_while(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=3) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable condition = argv[1];
	if( (condition.type != tInline) &&
			(condition.type != tVarname) &&
			(condition.type != tNumeric)) {
		throw_error(ERROR_ARGUMENTS);
	}
	Variable cond = condition;
	if(condition.type == tVarname) {
		cond = env.getvar(condition.str);
	} else if(condition.type == tInline) {
		cond = orate(condition.str,env,DISABLESCOPE);
	}
	Variable code = argv[2];
	if(code.type != tInline) {
		throw_error(ERROR_INLINECODE);
	}
	while( cond.i ) {
		orate(code.str,env,DISABLESCOPE);
		ret = env.getvar("__return__");
		if(condition.type == tVarname) {
			cond = env.getvar(condition.str);
		} else if(condition.type == tInline) {
			cond = orate(condition.str,env,DISABLESCOPE);
		}
		if(env.flags & Break_Scope) {
			env.flags ^= Break_Scope;
			break;
		}
	}
	return ret;
}

Variable bi_break(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=1) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	env.flags |= Break_Scope;
	return ret;
}

Variable bi_local(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc==1) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	for(int i=1;i<argc;i++) {
		ret=env._var.back()[argv[i].str]=env.getvar(argv[i].str);
	}
	return ret;
}

Variable bi_global(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc==1) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	for(int i=1;i<argc;i++) {
		env._var.back().erase(argv[i].str);
	}
	return ret;
}

Variable bi_sprintf(vector<Variable>& argv, Environment& env)
{
	stringstream ss;
	int argc = (int)argv.size();
	if(argc <= 2) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	if(argv[1].type != tVarname) {
		throw_error(ERROR_ARGUMENTS);
	}
	string varname = argv[1].str;
	argv.erase(argv.begin() + 1);
	_bi_printto(argv,env,ss);
	env.getvar(varname) = ss.str();
	Variable ret = ss.str();
	return ret;
}

Variable bi_error(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc==2) {
		ret = argv[1];
		env.verify(ret);
		ret = ErrorMessage((ErrorType)(int)ret.i);
	} else if(argc==1) {
		ret = ErrorMessage(ERROR_UNKNOWN);
	} else {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	return ret;
}

Variable bi_const(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=3) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	vector<Variable>tmp(1);
	Variable name = argv[1];
	if(name.type != tVarname) {
		throw_error(ERROR_ARGUMENTS);
	}
	tmp[0] = argv[2];
	env.verify(tmp[0]);
	env.repl[name.str]=tmp;
	return ret;
}

Variable bi_deconst(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=2) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable name = argv[1];
	env.repl.erase(name.str);
	return ret;
}
